#define CNAME_MAP 0
#define BLIND_MAP 1
#define RCOUNT_MAP 2
#define JCLAUSE_MAP 3
#define SKIP_MAP -1

qi_count_map * return_map( unsigned map_ref, const bool& clear = false ){

	static qi_count_map found_columns, total_count,
			restriction_count, join_count;

	if( clear ){
		found_columns.clear(); total_count.clear();
		restriction_count.clear(); join_count.clear();
		return NULL;
	}

	if( map_ref == CNAME_MAP ) return &found_columns;
	else if( map_ref == BLIND_MAP ) return &total_count;
	else if( map_ref == RCOUNT_MAP ) return &restriction_count;
	else if( map_ref == JCLAUSE_MAP ) return &join_count;
	else assert( map_ref < 4 ); // Never occurs.

	{ return NULL; /* Merely to avoid a warning. */ }

}

void count( const string& s, const string& d,
		const pair<strings,strings>& t, const unsigned& map_ref ){

	// PENDING TASK: FIX INTERPRETATION OF ASTERISKS.
	strings v = tokenize( s, true, false, " \t,;(<>)+^=\"|*[]" );
	qi_count_map * map = return_map( map_ref );

	for( int i = 0, l = v.size(); i < l; i++ ){

		// Uppercase Qualified Identity!
		string u_v = to_upper_copy( v.at( i ) );

		// Forced hack to maintain
		// quotes in column names.
		static const regex then_quote( "THEN'" );
		u_v = regex_replace( u_v, then_quote, "'" );

		two_strings t_found;
		bool consider = true;

		string tn, cn; // Inferred table and column names.
		static const RE table_column( "([^\\.]+)\\.+([A-Z]+[^\\.]+)", icase );

		if( table_column.FullMatch( u_v, &tn, &cn ) ){

			// Check if tn is present in the aliases list and put it as
			// the table found, else put a blank table name in t_found.
			int index = 0, l = t.second.size();
			for( ; index < l; index++ )
				if( t.second.at( index ) == tn ) break;
			if( index == l ) t_found = make_pair( blank, tn );
			else t_found = make_pair( t.first.at( index ), t.second.at( index ) );

			// Table prefix has to be removed
			u_v = cn; // from the column name.

		}
		else if( !t.first.empty() ){

			// Assert that there is only one table in
			// the list (and put that one as t_found).
			t_found = make_pair( t.first.at(0), t.second.at(0) );

		}
		else consider = false;

		if( consider ){

			qualified_identity u_qi = make_triplet( d, t_found, u_v );
			if( map->count( u_qi ) == 1 ) (*map)[ u_qi ]++;
			else map->insert( make_pair( u_qi, 1 ) );

		}

	}

}

// Restores original state of the extracted stuff.
// Replaces the "replacement" character with the
// original "irritant" keeping any quirks in mind
// (see <space_fixes.hpp>: look for "<<1>>" to get
// the corresponding comment).
inline string restore_copy( const string& internally_modded ){
	string restored = replace_all_copy( internally_modded, "???", " " );
	replace_all( restored, "?", " " ); trim( restored ); return restored;
}

void print_outputs( const int& num_param2, const bool& continued ){

	ofstream output1;
	if( continued ) output1.open( "output1.txt", ofstream::app );
	else output1.open( "output1.txt" );
	assert( output1.is_open() );

	ofstream output2;
	if( continued ) output2.open( "output2.txt", ofstream::app );
	else output2.open( "output2.txt" );
	assert( output2.is_open() );

	bool startedO2 = false;

	ofstream output3;
	if( continued ) output3.open( "output3.txt", ofstream::app );
	else output3.open( "output3.txt" );
	assert( output3.is_open() );

	if( continued ){
		output1 << endl;
		output2 << endl;
		output3 << endl;
	}

	/* const qi_count_map& found_columns, const qi_count_map& total_count,
		const qi_count_map& restriction_count, const qi_count_map& join_count */

	string last_db = blank;
	string last_tn = blank;
	string last_cn = blank;
	for( qi_count_pair qi: (*(return_map(0))) ){

		string cn = restore_copy( qi.first.second.second );
		if( cn == blank or cn == string( "E#M#P#T#Y" ) ) continue;

		string tn = restore_copy( qi.first.second.first.first );
		if( tn == string( "S#E#L#E#C#T#I#O#N" ) )
			tn = restore_copy( qi.first.second.first.second );
		// What about checking if alias is sane??
		if( tn == blank ) continue;

		string db = restore_copy( qi.first.first );

		/* STRUCTURE OF CSV FILE (SLIGHTLY EXPANDED FROM
		   THE SPECIFICATIONS IN DOCUMENT) FOR OUTPUT #1:
		   <database_name>, <table_name>, <column_name>,
		   <total_count>, <restriction_count>, <join_count>
		   (NOTE: SPACES HERE ARE MERELY INDICATIVE!) */

		//assert( (return_map(1))->count( qi.first ) == 1 );

		// Filtering can be implemented in output1 too.
		// if( (*(return_map(1)))[ qi.first ] > 1 ) ...

		/* OUTPUT #1 */

		// All fields upto the total count.
		output1 << db << "," << tn << "," << cn << ",";
		output1 << (*(return_map(1)))[ qi.first ] << ",";

		int rc = 0; // Restriction Count
		if( (return_map(2))->count( qi.first ) == 1 )
			rc = (*(return_map(2)))[ qi.first ];
		output1 << rc << ",";

		int jc = 0; // Join Clause Count
		if( (return_map(3))->count( qi.first ) == 1 )
			jc = (*(return_map(3)))[ qi.first ];
		output1 << jc << endl;

		/* OUTPUT #2 */

		// CHECK IF THE STATEMENT IS VALID
		// ..TO GET THE DISPERSION DATA++
		if( rc+jc > num_param2 ){

			if( startedO2 ){
				if( !( db == last_db and tn == last_tn and cn == last_cn ) )
					// The output statement is preferably
					// multi-line for better readability.
					// Make changes here (AND IN THE ELSE
					// BLOCK) to format output #2 anyhow.
					output2 << endl << "UNION SELECT " << tn <<" AS TABLE_NAME, "
							<< cn << " AS COLUMN_NAME, ( 1 / COUNT (DISTINCT "
							<< cn << ") ) FROM " << tn;
			}
			else{ // ..since the UNION keyword is not
				  // to be used at the very beginning.
				startedO2 = true;
				output2 << "SELECT " << tn <<" AS TABLE_NAME, "
						<< cn << " AS COLUMN_NAME, ( 1 / COUNT (DISTINCT "
						<< cn << ") ) FROM " << tn;
			}

		}

		/* OUTPUT #3 */

		if( !( db == last_db and tn == last_tn ) )
			output3 << "SELECT " << tn << " AS TABLE_NAME, "
					<< "COUNT(1) AS ROW_COUNT FROM " <<  tn << ";" << endl;

		/* OUTPUTS OVER */

		last_db = db;
		last_tn = tn;
		last_cn = cn;

	}

	if( startedO2 ) output2 << ";" << endl;

	output1.close();
	output2.close();
	output3.close();

}
